Hiding Implementation in Libraries

1/5/24

There are some instances of SDL_Window and SDL_Renderer that are required to be referenced throughout the library. A window and corresponding renderer are needed throughout multiple SDL functions for graphics rendering. Planned components built on top of SDL like SpriteRenderer require ready access to them in order to render objects.

First thought was to make them global, but as global variables are generally discouraged, I explored options to minimize scope to only the classes that would need them. Eventually it was decided to make variables effectively global for the time being as the options turned out fruitless and overcomplicated.

Protected Static

One option was to create a renderer class with SDL objects as protected static variables so that they may be inherited by new user components like SpriteRenderer. This way we limit the scope of these variables to only classes that need it.

GameRenderer.h

#include <SDL.h>

/**
 * Globally available SDL window references for internal use.
 */
class GameRenderer {
public:
    // [... methods...]

protected:
    /**
     * SDL Renderer access
     */
    static const int SCREEN_WIDTH = 640;
    static const int SCREEN_HEIGHT = 480;

    static SDL_Window* window;
    static SDL_Renderer* renderer;
    static SDL_Surface* screenSurface;
};

However, we’ll find that user facing classes like SpriteRenderer cannot inherit from GameRenderer in this case, as doing so would require #include a header that contains #include <SDL.h>, thereby exposing implementation to the user as well as requiring the library user to also install SDL.

Pointer to Implementation

Another option was exploring a variation of the PImpl pattern, where a forward declaration of an implementation class is made within parent class alongside a pointer reference to the class that can be used.

Intuition/Incentive

Implementation of an interface will usually change more often than the interface itself. Because private members (i.e. implementation details) are part of a class definition in the header file, changing private members will change the class definition even though there are no public user facing changes to the interface.

This will require recompilation on part of the user even though the only hidden from user implementation details were changed.

The common pattern/solution in cpp is to declare an implementation class along with a pointer to an instance to the class. Declaration of private methods/members are now effectively moved to .cpp files and so modification of private variables would only require recompilation of the library and not any library users applications.

Variation

While the library being built is small and compilation time and dependencies are not really an issue, I did find that the further hiding the implementation details aspect of this pattern potentially useful for solving the issue with [[#Protected Static]]. At first glance this seemed like a good way to have protected SDL variables without them being part of the declaration header.

GameRenderer.h

class GameRenderer {
public:
    // [... methods...]
protected:
    /**
     * Forward declaration to hide SDL implementation details.
     */
    class GameRendererImpl;
    static std::unique_ptr<GameRendererImpl> pImpl;
};

GameRenderer.cpp

class GameRenderer::GameRendererImpl
{
public:
    GameRendererImpl();
    ~GameRendererImpl();

    const int SCREEN_WIDTH = 640;
    const int SCREEN_HEIGHT = 480;

    SDL_Window* window = nullptr;
    SDL_Renderer* renderer = nullptr;
    SDL_Surface* screenSurface = nullptr;
};

//______GameRenderer definitions______//
//
// [...code...]

//___GameRendererImpl definitions_____//
GameRenderer::GameRendererImpl::GameRendererImpl()
{
    // [...SDL code...]
}

GameRenderer::GameRendererImpl::~GameRendererImpl()
{
    // [...SDL code...]
}

However, while SDL implementation details are hidden from the user, we encounter an issue where implementation details of this class are also hidden from other classes of the library. Other classes defined in other .cpp files that inherit from GameRenderer also have no way of accessing the SDL members of the GameRendererImpl instance as the implementation is no longer declared in the header.

This appears to be the tradeoff of using the pImpl pattern, and in this case prohibits it from being a viable way of reducing the scope of these SDL window and renderer variables.

Global

With both these options not working, I opted to make the variables in effect global by encasing them as static variables in the GameRenderer class. The header for this class would not be included in the library, and only included in library implementation files to those that need it.

This way SDL is still hidden from the user, and library files that require access to the SDL variables need only include this header file.

While this could have been done in the first place to save time, the search process for a smaller scope solution was still a nice learning experience on how libraries need to be structured and various techniques available to hide implementation details.

GameRenderer.h

#include <SDL.h>

/**
 * Globally available SDL window references for internal use.
 */
class GameRenderer {
public:
    // [... methods...]
    /**
     * SDL Renderer access
     */
    static const int SCREEN_WIDTH = 640;
    static const int SCREEN_HEIGHT = 480;

    static SDL_Window* window;
    static SDL_Renderer* renderer;
    static SDL_Surface* screenSurface;
};